﻿using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using System.Xml;
using Microsoft.WowAddonStudio.FrameXml.Components.Support;
using Microsoft.WowAddonStudio.FrameXml.Components.Tests.Forms;
using Microsoft.WowAddonStudio.FrameXml.Components.Tests.Properties;
using Microsoft.WowAddonStudio.FrameXml.Formats.Mpq;
using Microsoft.WowAddonStudio.FrameXml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.Support;
using Microsoft.WowAddonStudio.FrameXml.Services;
using Microsoft.WowAddonStudio.Tests.Common.Mocks;

using NUnit.Framework;

namespace Microsoft.WowAddonStudio.FrameXml.Components.Tests
{
	[TestFixture]
	public class FrameXmlControlTestBase
	{
		private const string tocFileRelativePath = @"Blizzard Interface Data (enGB)\FrameXML\FrameXML.toc";
		private const string mpqArchiveRelativePath = @"Data\enGB\locale-enGB.MPQ";
		private const string frameXmlRelativePath = @"Blizzard Interface Data (enGB)\FrameXML";
		private const string schemaPath = "Data/UI.xsd";
		protected const string TestXmlFileName = "test.xml";

		private readonly string assemblyPath = Path.Combine(Environment.CurrentDirectory, "FrameXml.Components.dll");

		private const string resourceRootPath =
			"Microsoft.WowAddonStudio.FrameXml.Components.Resources.SerializationObjectFactory";

		private readonly Size defaultUiSize = new Size(300, 300);

		protected readonly FrameXmlLoader frameXmlLoader = new FrameXmlLoader {SchemaPath = schemaPath};
		protected FrameXmlComponentFactory frameXmlComponentFactory;

		private ResourceFileLoader resourceFileLoader;
		private FrameXmlContainer frameXmlContainer;

		private string mpqArchivePath;

		/// <summary>
		/// Sets up.
		/// </summary>
		[SetUp]
		public virtual void SetUp()
		{
			if (!Directory.Exists(Settings.Default.WowBasePath))
				Assert.Ignore("Cannot run test. Please set the World of Warcraft base path.");

			mpqArchivePath = Path.Combine(Settings.Default.WowBasePath, mpqArchiveRelativePath);

			// Create resource file loader
			resourceFileLoader = new ResourceFileLoader();

			// Add WoW base path and the MPQ archive
			resourceFileLoader.BasePaths.Add(Settings.Default.WowBasePath);
			resourceFileLoader.MpqArchives.Add(new MpqArchive(mpqArchivePath));

			// Create FrameXML container
			frameXmlContainer = new FrameXmlContainer(resourceFileLoader);

			var locator = new SimpleTestServiceLocator();

			// Create component factory
			frameXmlComponentFactory = new FrameXmlComponentFactory(new ReflectionComponentActivator(),
			                                                        frameXmlContainer, locator);

			// Create an FrameXmlDirectory instance for the test
			FrameXmlDirectory.Default = new FrameXmlDirectory();
		}

		/// <summary>
		/// Tears down.
		/// </summary>
		[TearDown]
		public virtual void TearDown()
		{
			if (resourceFileLoader != null)
				resourceFileLoader.Dispose();

			Process.GetCurrentProcess().Close();
		}

		/// <summary>
		/// Gets the <see cref="UiControl"/>.
		/// </summary>
		protected UiControl UiControl { get; private set; }


		/// <summary>
		/// Initializes the frame XML directory.
		/// </summary>
		protected void RegisterWowTocFile()
		{
			string frameXmlDirectory = Path.Combine(Settings.Default.WowBasePath, frameXmlRelativePath);
			if (resourceFileLoader != null)
			{
				string tocFilePath = Path.Combine(Settings.Default.WowBasePath, tocFileRelativePath);
				if (resourceFileLoader.ResourceFileExists(tocFilePath))
				{
					using (var tocFileStream = resourceFileLoader.GetResourceFileStream(tocFilePath))
					{
						foreach (var frameXmlFile in FrameXmlDirectory.GetFrameXmlFiles(tocFileStream))
						{
							string frameXmlPath = Path.Combine(frameXmlDirectory, frameXmlFile);
							if (resourceFileLoader.ResourceFileExists(frameXmlPath))
                                FrameXmlDirectory.RegisterFrameXmlFile(frameXmlPath, path => resourceFileLoader.GetResourceFileStream(path));
						}
					}
				}
			}
		}

		/// <summary>
		/// Initializes the UI.
		/// </summary>
		/// <param name="frameXmlPath">The frame XML path.</param>
		/// <param name="size">The size.</param>
		protected void InitializeUi(string frameXmlPath, Size size)
		{
			var ui = frameXmlLoader.LoadUi(frameXmlPath);

			UiControl = frameXmlComponentFactory.CreateComponent<Ui, UiControl>(ui);
			UiControl.Size = size;
		}

		/// <summary>
		/// Initializes the UI.
		/// </summary>
		/// <param name="frameXmlPath">The frame XML path.</param>
		protected void InitializeUi(string frameXmlPath)
		{
			InitializeUi(frameXmlPath, defaultUiSize);
		}

		/// <summary>
		/// Initializes the UI.
		/// </summary>
		/// <param name="frameXmlFile">The frame XML path.</param>
		protected void InitializeUi(Stream frameXmlFile)
		{
			var ui = frameXmlLoader.LoadUi(frameXmlFile);

			UiControl = frameXmlComponentFactory.CreateComponent<Ui, UiControl>(ui);
			UiControl.Size = defaultUiSize;
		}

		/// <summary>
		/// Asserts the control.
		/// </summary>
		/// <param name="control">The control.</param>
		protected static void AssertControl(Control control)
		{
			Application.EnableVisualStyles();

			using (var testBenchForm = new FrameXmlTestBenchForm(control, false))
			{
				if (testBenchForm.ShowDialog() == DialogResult.No)
					Assert.Fail("FrameXML was manually rejected on the FrameXML Test Bench.");
			}
		}


		/// <summary>
		/// Gets the XML resource.
		/// </summary>
		/// <param name="serializationObjectType">The type.</param>
		/// <returns></returns>
		protected Stream GetXmlResource(Type serializationObjectType)
		{
			if (serializationObjectType == null)
				throw new ArgumentNullException("serializationObjectType");
			if (typeof (SerializationObject).IsSubclassOf(serializationObjectType))
				throw new ArgumentException("Type is not a subclass of SerializationObject.");

			// Construct path to resource
			var resourcePath = String.Format("{0}.{1}.xml", resourceRootPath, serializationObjectType.Name);

			// Get manifest resource stream and deserialize serialization object
			using (Stream stream = Assembly.LoadFile(assemblyPath).GetManifestResourceStream(resourcePath))
			{
				if (stream == null)
					throw new InvalidOperationException("stream");

				var xmlDocument = new XmlDocument();
				xmlDocument.Load(stream);

				var ui = new XmlDocument();
				ui.LoadXml(File.ReadAllText("Data\\UiTemplate.xml"));
				var navigator = ui.CreateNavigator();
				var manager = new XmlNamespaceManager(navigator.NameTable);
				manager.AddNamespace("wow", "http://www.blizzard.com/wow/ui/");

				var fragment = ui.CreateDocumentFragment();
				fragment.InnerXml = xmlDocument.OuterXml;
				var rootNode = ui.SelectSingleNode("wow:Ui", manager);

				rootNode.AppendChild(fragment);

				var memoryStream = new MemoryStream();
				{
					ui.Save(memoryStream);
					memoryStream.Position = 0;

					return memoryStream;
				}
			}
		}

		#region Helper Functions

		/// <summary>
		/// Asserts the text lines.
		/// </summary>
		/// <param name="name">The name.</param>
		protected static void AssertTextLines(string name)
		{
			Console.WriteLine(new string('*', 40));
			string text = File.ReadAllText(TestXmlFileName);
			Console.WriteLine(text);
			Assert.IsFalse(text.Contains(name));
		}

		/// <summary>
		/// Inits the controls.
		/// </summary>
		/// <param name="component">The component.</param>
		protected virtual void WriteControls(Control component)
		{
			if (component == null) return;

			if (component is LayoutFrameControl)
			{
				Trace.WriteLine(String.Format("{0}[{1}]", component.Name,
				                              ((LayoutFrameControl) component).LayoutFrame.Name));
			}

			if (component.Controls.Count > 0)
			{
				Trace.Indent();
				foreach (Control control in component.Controls)
					WriteControls(control);
				Trace.Unindent();
			}
		}

		#endregion
	}
}